package la.iok.aone.monitor.youtube.schedule;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import la.iok.aone.monitor.youtube.model.YoutubeLink;
import la.iok.aone.monitor.youtube.service.YoutubeLinkService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.client.RestTemplate;

import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Youtube链接同步定时器，用于定时从龙视平台获取Youtube链接。
 * @author bunco
 * @date 2018年6月5日 10时20分
 * @version 1.0
 */
@Component
public class SyncYoutubeLink {
    private Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * YouTube网址
     * 默认值：https://www.youtube.com
     */
    @Value("${config.sync.youtube.domain:https://www.youtube.com}")
    private String youtubeDomain;
    /**
     * YouTube网站视频链接地址
     * 默认值：/watch?
     */
    @Value("${config.sync.youtube.domain.interface:/watch?}")
    private String youtubeDomainWatch;
    /**
     * YouTube视频链接视频ID参数名
     * 默认值：v
     */
    @Value("${config.sync.youtube.domain.interface.key.video.id:v}")
    private String youtubeVideoIdKey;
    /**
     * 监控链接获取接口服务器地址
     * 默认值：http://web4.long.tv
     */
    @Value("${config.sync.api.server:http://web4.long.tv}")
    private String syncApiServer;
    /**
     * 监控链接获取接口API地址
     * 默认值：/index.php?route=longtv/polling_check_link&pagesize={1}&pageindex={2}
     */
    @Value("${config.sync.api.interface.get.link:/index.php?route=longtv/polling_check_link}")
    private String syncInterfaceGetLink;
    /**
     * 监控链接获取接口API页大小参数名
     * 默认值：pagesize
     */
    @Value("${config.sync.api.interface.get.link.param.key.page.size:pagesize}")
    private String paramKeyPageSize;
    /**
     * 监控链接获取接口API页码参数名
     * 默认值：pageindex
     */
    @Value("${config.sync.api.interface.get.link.param.key.page.index:pageindex}")
    private String paramKeyPageIndex;
    /**
     * 监控链接获取接口API默认页开始索引
     * 默认值：1
     */
    @Value("${config.sync.api.interface.get.link.start.page.no:1}")
    private int startPageNo;
    /**
     * 监控链接获取接口API每页数据数量
     * 默认值：20
     */
    @Value("${config.sync.api.interface.get.link.page.size:20}")
    private int pageSize;
    /**
     * 监控链接获取数据的JSON键名
     * 默认值：data
     */
    @Value("${config.sync.api.interface.get.link.json.key.data:data}")
    private String jsonKeyData;
    /**
     * 监控链接获取状态的JSON键名
     * 默认值：status
     */
    @Value("${config.sync.api.interface.get.link.json.key.status:status}")
    private String jsonKeyStatus;
    /**
     * 监控链接获取总页数的JSON键名
     * 默认值：totalPages
     */
    @Value("${config.sync.api.interface.get.link.json.key.total.pages:totalPages}")
    private String jsonKeyTotalPages;
    /**
     * 监控链接获取总记录数的JSON键名
     * 默认值：totalCount
     */
    @Value("${config.sync.api.interface.get.link.json.key.total.records:totalCount}")
    private String jsonKeyTotalRecords;
    /**
     * 监控链接获取链接的JSON键名
     * 默认值：product_url
     */
    @Value("${config.sync.api.interface.get.link.json.key.link:product_url}")
    private String jsonKeyLink;
    /**
     * 监控链接获取节目名称的JSON键名
     * 默认值：product_name
     */
    @Value("${config.sync.api.interface.get.link.json.key.title:product_name}")
    private String jsonKeyTitle;
    /**
     * 监控链接获取节目分类数组的JSON键名
     * 默认值：categorys
     */
    @Value("${config.sync.api.interface.get.link.json.key.category:categorys}")
    private String jsonKeyCategory;
    /**
     * 从接口获取每一页数据的间隔（单位：秒）
     * 默认值：1
     */
    @Value("${config.sync.fetch.each.page.interval:5}")
    private int interval;

    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private YoutubeLinkService youtubeLinkService;
//    @Autowired
//    private ThreadPoolTaskExecutor executor;

    /**
     * 每隔一段时间，将通过龙视接口获取到所有链接地址，保存到数据库中。
     * 默认同步时间为周一至周五北京时间8点~20点之间，每15分钟同步一次增量数据。
     */
    @Scheduled(cron = "${config.sync.schedule.cron:0 */15 8-20 ? * MON-FRI}")
    public void syncYoutubeLink(){
        logger.info("syncYoutubeLink =====>>>>> cron: {}, api server: {}",
                new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()), syncApiServer+syncInterfaceGetLink);

        /**
         * 查询数据库中所有链接数量，除以页大小，得出已经获取到的页数数据。再加上起始页码。
         * 这样每次只需要处理最后一页的重复数据和增量数据。
         */
        int pageNo = (this.youtubeLinkService.countAll() / pageSize) + startPageNo;
        int currentPageSize = 0;
        do {
            String url = MessageFormat.format(syncInterfaceGetLink, pageSize, pageNo);
            logger.info("Interface: {}{}", syncApiServer, url);
            String jsonString = this.restTemplate.getForObject(syncApiServer+url, String.class);
            logger.debug("{}", jsonString);
            JSONObject jsonObject = JSON.parseObject(jsonString);
            // 如果获取到链接数据状态为false 抛出异常，不再去解析数据。
            Assert.isTrue(jsonObject.getBoolean(jsonKeyStatus), "Got status is false!");
            JSONObject data = jsonObject.getJSONObject(jsonKeyData);
            Set<String> linkIds = data.keySet();
            /**
             * 将当前页获取到的链接编号数量赋值给currentPageSize，如果该变量值等于页大小则可能存在下一页数据，继续循环。
             * 如果该变量值小于页大小，则表示当前为最后一页数据，跳出循环。
             * 但不排除中间某一页数据小于页大小的情况，则会导致只能获取到前面一部分链接数据。
             */
            currentPageSize = linkIds.size();
            /**
             * 删除总页数和总记录数两个键。这两个不是属性不是JSON对象，会导致解析错误。
             */
            linkIds.remove(jsonKeyTotalPages);
            linkIds.remove(jsonKeyTotalRecords);
            // 排一下序，使得获取的编号按JSON顺序插入数据库。方便排错。
            linkIds.stream().sorted();
            for (String linkId: linkIds) {
                JSONObject linkObject = data.getJSONObject(linkId);

                YoutubeLink youtubeLink = new YoutubeLink();
                youtubeLink.setFeedbackId(linkId);
                try {
                    youtubeLink.setParseLink(linkObject.getJSONArray(jsonKeyLink).stream().map(
                            Object::toString).collect(Collectors.joining(",")),
                            youtubeDomain, youtubeDomainWatch, youtubeVideoIdKey);
                } catch (IllegalArgumentException e) {
                    // 当链接视频检测出异常之后，记录错误信息。仍然保存到数据库中，为了保持和龙视数据库记录同步。
                    logger.error("视频链接解析出错：", e);
                }
                youtubeLink.setTitle(linkObject.getString(jsonKeyTitle));

                youtubeLink.setCategory(linkObject.getJSONArray(jsonKeyCategory).stream().map(
                        Object::toString).collect(Collectors.joining(",")));

                /**
                 * 丢到线程池里面去保存到数据库，看效率是否会有所提升
                 */
//                executor.execute(new Runnable() {
//                    @Override
//                    public void run() {
//                        try {
//                            youtubeLinkService.insertOrUpdate(youtubeLink);
//                        } catch (Exception e) {
//                            logger.error("保存数据出错：", e);
//                            logger.error("出错数据：{}", youtubeLink);
//                        }
//                    }
//                });
                try {
                    youtubeLinkService.insertOrUpdate(youtubeLink);
                } catch (Exception e) {
                    logger.error("保存数据出错：", e);
                    logger.error("出错数据：{}", youtubeLink);
                }
            }
            pageNo++;
//            logger.info("TaskExecutor poolSize: {}, activeCount: {}, taskCount: {}, queueSize: {}, largestPoolSize: {}",
//                    executor.getPoolSize(), executor.getActiveCount(),
//                    executor.getThreadPoolExecutor().getTaskCount(),
//                    executor.getThreadPoolExecutor().getQueue().size(),
//                    executor.getThreadPoolExecutor().getLargestPoolSize());
//            try {
//                // 休眠两秒，避免频繁请求导致请求不成功。也避免大量任务丢进线程池导致执行不过来。
//                Thread.sleep(interval * 1000);
//            } catch (InterruptedException e) {
//                logger.error("", e);
//            }
        } while (currentPageSize >= pageSize); // 当某一页数据小于默认页大小数据时终止循环
    }
}
